from flask import Flask, request, jsonify
from models.hybrid.hybrid_recommender import HybridRecommender
from models.model_updater import ModelUpdater
import threading
import time
import json
from config.config import RECOMMENDATION_CONFIG
from utils.db import get_mysql_conn, get_redis_conn, close_all_connections

# 全局变量
hybrid_recommender = None
model_updater = None

def initialize_models():
    """初始化推荐模型"""
    global hybrid_recommender, model_updater
    
    print("正在初始化推荐模型...")
    
    try:
        # 确保数据库连接正常
        # 关闭并重建所有连接
        close_all_connections()
        
        # 测试数据库连接
        mysql_conn = get_mysql_conn(force_new=True)
        if not mysql_conn:
            print("MySQL连接失败，无法初始化推荐模型")
            return False
        
        redis_conn = get_redis_conn(force_new=True)
        if not redis_conn:
            print("Redis连接失败，无法初始化推荐模型")
            return False
        
        # 创建混合推荐器
        hybrid_recommender = HybridRecommender()
        
        # 创建模型更新器
        model_updater = ModelUpdater()
        
        # 启动定时更新任务
        model_updater.start()
        
        print("推荐模型初始化完成")
        return True
        
    except Exception as e:
        print(f"初始化模型时出错: {e}")
        import traceback
        traceback.print_exc()
        return False

# 封装Flask应用为服务类
class RecommendationService:
    """推荐服务类，封装Flask应用和模型管理"""
    
    def __init__(self):
        self.app = Flask(__name__)
        self.initialize_routes()
        
        # 初始化模型
        success = initialize_models()
        if not success:
            raise Exception("无法初始化推荐模型")
    
    def initialize_routes(self):
        """初始化API路由"""
        
        @self.app.route('/recommend', methods=['GET'])
        def recommend():
            try:
                # 获取请求参数
                user_id = request.args.get('user_id', type=int)
                count = request.args.get('count', default=10, type=int)
                
                if not user_id:
                    return jsonify({"error": "缺少必要参数：user_id"}), 400
                
                # 调用推荐器获取推荐结果
                if hybrid_recommender:
                    results = hybrid_recommender.recommend(user_id, count)
                    return jsonify({"success": True, "data": results})
                else:
                    return jsonify({"error": "推荐服务未初始化"}), 500
                    
            except Exception as e:
                print(f"生成推荐时发生错误: {e}")
                return jsonify({"error": f"推荐失败: {str(e)}"}), 500
        
        @self.app.route('/health', methods=['GET'])
        def health_check():
            """健康检查接口"""
            status = {
                "status": "healthy",
                "hybrid_recommender": hybrid_recommender is not None,
                "model_updater": model_updater is not None
            }
            return jsonify(status)
        
        @self.app.route('/similar-courses', methods=['GET'])
        def similar_courses():
            try:
                # 获取请求参数
                course_id = request.args.get('course_id', type=int)
                count = request.args.get('count', default=5, type=int)
                
                if not course_id:
                    return jsonify({"error": "缺少必要参数：course_id"}), 400
                
                # 调用推荐器获取相似课程
                if hybrid_recommender:
                    results = hybrid_recommender.get_similar_courses(course_id, count)
                    return jsonify({"success": True, "data": results})
                else:
                    return jsonify({"error": "推荐服务未初始化"}), 500
                    
            except Exception as e:
                print(f"获取相似课程时发生错误: {e}")
                return jsonify({"error": f"获取相似课程失败: {str(e)}"}), 500
                
        @self.app.route('/api/recommendation/course', methods=['GET'])
        def recommend_courses():
            """
            推荐课程API
            
            请求参数:
                user_id: 用户ID
                count: 推荐数量，默认为10
                
            返回:
                推荐课程列表
            """
            try:
                # 获取请求参数
                user_id = request.args.get('user_id', type=int)
                count = request.args.get('count', default=10, type=int)
                
                # 参数验证
                if not user_id:
                    return jsonify({
                        'code': 400,
                        'message': '缺少必要参数: user_id'
                    }), 400
                
                # 从混合推荐器获取推荐结果
                recommendations = hybrid_recommender.recommend(user_id, n=count)
                
                # 处理推荐结果
                result = []
                for rec in recommendations:
                    if len(rec) == 2:
                        course_id, score = rec
                        result.append({
                            'course_id': course_id,
                            'score': score
                        })
                    else:
                        course_id, score, info = rec
                        result.append({
                            'course_id': course_id,
                            'score': score,
                            'title': info.get('title', ''),
                            'category': info.get('category', ''),
                            'tags': info.get('tags', ''),
                            'difficulty': info.get('difficulty', ''),
                            'teacher_name': info.get('teacher_name', ''),
                            'avg_rating': float(info.get('avg_rating', 0)) if info.get('avg_rating') else None
                        })
                
                # 将推荐结果保存到Redis缓存
                hybrid_recommender.save_recommendation_to_redis(user_id, [(r['course_id'], r['score']) for r in result])
                
                return jsonify({
                    'code': 200,
                    'message': 'success',
                    'data': {
                        'user_id': user_id,
                        'recommendations': result
                    }
                })
                
            except Exception as e:
                print(f"推荐课程失败: {e}")
                return jsonify({
                    'code': 500,
                    'message': f'推荐失败: {str(e)}'
                }), 500
        
        @self.app.route('/api/recommendation/similar_courses', methods=['GET'])
        def get_similar_courses():
            """
            获取相似课程API
            
            请求参数:
                course_id: 课程ID
                count: 推荐数量，默认为5
                
            返回:
                相似课程列表
            """
            try:
                # 获取请求参数
                course_id = request.args.get('course_id', type=int)
                count = request.args.get('count', default=5, type=int)
                
                # 参数验证
                if not course_id:
                    return jsonify({
                        'code': 400,
                        'message': '缺少必要参数: course_id'
                    }), 400
                
                # 获取相似课程
                # 使用协同过滤模型获取相似课程
                if course_id in hybrid_recommender.item_cf.item_similarity_matrix:
                    similar_courses = hybrid_recommender.item_cf.item_similarity_matrix[course_id]
                    recommendations = sorted(similar_courses.items(), key=lambda x: x[1], reverse=True)[:count]
                    
                    # 处理推荐结果
                    result = []
                    for course_id, similarity in recommendations:
                        result.append({
                            'course_id': int(course_id),
                            'similarity': similarity
                        })
                    
                    return jsonify({
                        'code': 200,
                        'message': 'success',
                        'data': {
                            'course_id': course_id,
                            'similar_courses': result
                        }
                    })
                else:
                    return jsonify({
                        'code': 404,
                        'message': f'未找到课程 {course_id} 的相似课程'
                    }), 404
                    
            except Exception as e:
                print(f"获取相似课程失败: {e}")
                return jsonify({
                    'code': 500,
                    'message': f'获取相似课程失败: {str(e)}'
                }), 500
                
        @self.app.route('/api/recommendation/status', methods=['GET'])
        def get_status():
            """
            获取推荐引擎状态API
            
            返回:
                推荐引擎状态信息
            """
            try:
                # 获取协同过滤模型信息
                cf_info = {
                    'loaded': hasattr(hybrid_recommender.item_cf, 'item_similarity_matrix'),
                    'course_count': len(hybrid_recommender.item_cf.item_similarity_matrix) if hasattr(hybrid_recommender.item_cf, 'item_similarity_matrix') else 0
                }
                
                # 获取内容推荐模型信息
                content_info = {
                    'loaded': hasattr(hybrid_recommender.content_recommender, 'content_similarity'),
                    'course_count': len(hybrid_recommender.content_recommender.content_similarity) if hasattr(hybrid_recommender.content_recommender, 'content_similarity') else 0
                }
                
                # 获取配置信息
                config_info = {
                    'cf_weight': hybrid_recommender.cf_weight,
                    'content_weight': hybrid_recommender.content_weight,
                    'update_time': RECOMMENDATION_CONFIG.get('model_update_time', '01:00')
                }
                
                return jsonify({
                    'code': 200,
                    'message': 'success',
                    'data': {
                        'cf_model': cf_info,
                        'content_model': content_info,
                        'config': config_info,
                        'updater_running': model_updater.running if model_updater else False
                    }
                })
                
            except Exception as e:
                print(f"获取状态信息失败: {e}")
                return jsonify({
                    'code': 500,
                    'message': f'获取状态信息失败: {str(e)}'
                }), 500
                
        @self.app.route('/api/recommendation/update_models', methods=['POST'])
        def update_models():
            """
            手动触发模型更新API
            
            请求体:
                {
                    "token": "your_secret_token"  // 安全令牌
                }
                
            返回:
                更新结果
            """
            try:
                # 获取请求数据
                data = request.get_json()
                
                # 验证安全令牌
                token = data.get('token', '')
                expected_token = "your_secret_token"  # 实际应用中应使用配置或环境变量
                
                if token != expected_token:
                    return jsonify({
                        'code': 403,
                        'message': '无权访问'
                    }), 403
                
                # 在后台线程中执行模型更新
                def update_task():
                    model_updater.update_all_models()
                
                threading.Thread(target=update_task).start()
                
                return jsonify({
                    'code': 200,
                    'message': '模型更新任务已启动，将在后台执行'
                })
                
            except Exception as e:
                print(f"触发模型更新失败: {e}")
                return jsonify({
                    'code': 500,
                    'message': f'触发模型更新失败: {str(e)}'
                }), 500
    
    def start(self, host='0.0.0.0', port=5001, debug=False):
        """启动Flask应用"""
        print(f"启动推荐服务，监听地址: {host}:{port}")
        self.app.run(host=host, port=port, debug=debug)

# 向后兼容，保留原有接口
app = Flask(__name__)

@app.route('/recommend', methods=['GET'])
def recommend():
    # 获取请求参数
    user_id = request.args.get('user_id', type=int)
    count = request.args.get('count', default=10, type=int)
    
    if not user_id:
        return jsonify({"error": "缺少必要参数：user_id"}), 400
    
    # 调用推荐器获取推荐结果
    if hybrid_recommender:
        results = hybrid_recommender.recommend(user_id, count)
        return jsonify({"success": True, "data": results})
    else:
        # 如果推荐器未初始化，尝试初始化
        if initialize_models():
            results = hybrid_recommender.recommend(user_id, count)
            return jsonify({"success": True, "data": results})
        else:
            return jsonify({"error": "推荐服务未初始化"}), 500

@app.route('/api/recommendation/course', methods=['GET'])
def recommend_courses():
    """
    推荐课程API
    
    请求参数:
        user_id: 用户ID
        count: 推荐数量，默认为10
        
    返回:
        推荐课程列表
    """
    try:
        # 获取请求参数
        user_id = request.args.get('user_id', type=int)
        count = request.args.get('count', default=10, type=int)
        
        # 参数验证
        if not user_id:
            return jsonify({
                'code': 400,
                'message': '缺少必要参数: user_id'
            }), 400
        
        # 从混合推荐器获取推荐结果
        recommendations = hybrid_recommender.recommend(user_id, n=count)
        
        # 处理推荐结果
        result = []
        for rec in recommendations:
            if len(rec) == 2:
                course_id, score = rec
                result.append({
                    'course_id': course_id,
                    'score': score
                })
            else:
                course_id, score, info = rec
                result.append({
                    'course_id': course_id,
                    'score': score,
                    'title': info.get('title', ''),
                    'category': info.get('category', ''),
                    'tags': info.get('tags', ''),
                    'difficulty': info.get('difficulty', ''),
                    'teacher_name': info.get('teacher_name', ''),
                    'avg_rating': float(info.get('avg_rating', 0)) if info.get('avg_rating') else None
                })
        
        # 将推荐结果保存到Redis缓存
        hybrid_recommender.save_recommendation_to_redis(user_id, [(r['course_id'], r['score']) for r in result])
        
        return jsonify({
            'code': 200,
            'message': 'success',
            'data': {
                'user_id': user_id,
                'recommendations': result
            }
        })
        
    except Exception as e:
        print(f"推荐课程失败: {e}")
        return jsonify({
            'code': 500,
            'message': f'推荐失败: {str(e)}'
        }), 500

@app.route('/api/recommendation/similar_courses', methods=['GET'])
def get_similar_courses():
    """
    获取相似课程API
    
    请求参数:
        course_id: 课程ID
        count: 推荐数量，默认为5
        
    返回:
        相似课程列表
    """
    try:
        # 获取请求参数
        course_id = request.args.get('course_id', type=int)
        count = request.args.get('count', default=5, type=int)
        
        # 参数验证
        if not course_id:
            return jsonify({
                'code': 400,
                'message': '缺少必要参数: course_id'
            }), 400
        
        # 获取相似课程
        # 使用协同过滤模型获取相似课程
        if course_id in hybrid_recommender.item_cf.item_similarity_matrix:
            similar_courses = hybrid_recommender.item_cf.item_similarity_matrix[course_id]
            recommendations = sorted(similar_courses.items(), key=lambda x: x[1], reverse=True)[:count]
            
            # 处理推荐结果
            result = []
            for course_id, similarity in recommendations:
                result.append({
                    'course_id': int(course_id),
                    'similarity': similarity
                })
            
            return jsonify({
                'code': 200,
                'message': 'success',
                'data': {
                    'course_id': course_id,
                    'similar_courses': result
                }
            })
        else:
            return jsonify({
                'code': 404,
                'message': f'未找到课程 {course_id} 的相似课程'
            }), 404
            
    except Exception as e:
        print(f"获取相似课程失败: {e}")
        return jsonify({
            'code': 500,
            'message': f'获取相似课程失败: {str(e)}'
        }), 500

@app.route('/api/recommendation/update_models', methods=['POST'])
def update_models():
    """
    手动触发模型更新API
    
    请求体:
        {
            "token": "your_secret_token"  // 安全令牌
        }
        
    返回:
        更新结果
    """
    try:
        # 获取请求数据
        data = request.get_json()
        
        # 验证安全令牌
        token = data.get('token', '')
        expected_token = "your_secret_token"  # 实际应用中应使用配置或环境变量
        
        if token != expected_token:
            return jsonify({
                'code': 403,
                'message': '无权访问'
            }), 403
        
        # 在后台线程中执行模型更新
        def update_task():
            model_updater.update_all_models()
        
        threading.Thread(target=update_task).start()
        
        return jsonify({
            'code': 200,
            'message': '模型更新任务已启动，将在后台执行'
        })
        
    except Exception as e:
        print(f"触发模型更新失败: {e}")
        return jsonify({
            'code': 500,
            'message': f'触发模型更新失败: {str(e)}'
        }), 500

@app.route('/api/recommendation/status', methods=['GET'])
def get_status():
    """
    获取推荐引擎状态API
    
    返回:
        推荐引擎状态信息
    """
    try:
        # 获取协同过滤模型信息
        cf_info = {
            'loaded': hasattr(hybrid_recommender.item_cf, 'item_similarity_matrix'),
            'course_count': len(hybrid_recommender.item_cf.item_similarity_matrix) if hasattr(hybrid_recommender.item_cf, 'item_similarity_matrix') else 0
        }
        
        # 获取内容推荐模型信息
        content_info = {
            'loaded': hasattr(hybrid_recommender.content_recommender, 'content_similarity'),
            'course_count': len(hybrid_recommender.content_recommender.content_similarity) if hasattr(hybrid_recommender.content_recommender, 'content_similarity') else 0
        }
        
        # 获取配置信息
        config_info = {
            'cf_weight': hybrid_recommender.cf_weight,
            'content_weight': hybrid_recommender.content_weight,
            'update_time': RECOMMENDATION_CONFIG.get('model_update_time', '01:00')
        }
        
        return jsonify({
            'code': 200,
            'message': 'success',
            'data': {
                'cf_model': cf_info,
                'content_model': content_info,
                'config': config_info,
                'updater_running': model_updater.running if model_updater else False
            }
        })
        
    except Exception as e:
        print(f"获取状态信息失败: {e}")
        return jsonify({
            'code': 500,
            'message': f'获取状态信息失败: {str(e)}'
        }), 500

def _enrich_recommendations(recommendations):
    """给推荐结果添加详细信息"""
    try:
        mysql_conn = get_mysql_conn()
        
        # 获取所有推荐课程ID
        course_ids = [str(course_id) for course_id, _ in recommendations]
        ids_str = ",".join(course_ids)
        
        # 查询课程详细信息 - 适应现有的数据库结构
        query = f"""
        SELECT c.id, c.title, cat.name as category, c.tags, c.price, 
               t.name as teacher_name, c.image_url,
               (SELECT AVG(CAST(JSON_EXTRACT(extra_data, '$.rating') AS DECIMAL(3,1)))
                FROM user_behaviors
                WHERE event_type = 'rate' AND course_id = c.id) AS avg_rating
        FROM courses c
        LEFT JOIN categories cat ON c.category_id = cat.id
        LEFT JOIN teachers t ON c.teacher_id = t.id
        WHERE c.id IN ({ids_str})
        """
        
        with mysql_conn.cursor() as cursor:
            cursor.execute(query)
            results = cursor.fetchall()
        
        # 创建课程信息字典
        course_info = {row['id']: row for row in results}
        
        # 为每个推荐添加详细信息
        for i in range(len(recommendations)):
            course_id, score = recommendations[i]
            if course_id in course_info:
                info = course_info[course_id]
                recommendations[i] = (course_id, score, info)
        
    except Exception as e:
        print(f"添加推荐详细信息失败: {e}")
        return recommendations

# 当作为独立模块运行时，初始化模型并启动服务
if __name__ == "__main__":
    initialize_models()
    app.run(host='0.0.0.0', port=5001, debug=False) 